/**
 * Copyright 2007-2008 Vidar Svansson
 *
 * This file is part of JAdapter.
 *
 * JAdapter is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JAdapter is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JAdapter.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.jadapter.registry;

import org.jadapter.Adapter;
import org.jadapter.exceptions.AdaptationException;
import org.jadapter.graph.Graph;

import java.util.Collection;
import java.util.HashSet;
import java.util.Arrays;
import java.lang.reflect.Type;
import java.lang.reflect.ParameterizedType;

/**
 * Data structure to store and retrieve adapter instances.
 * <br></br><br></br>
 * This is currently only accessible internally. The modifiers might be promoted to public
 * if a use case demands.
 *
 * @author Vidar Svansson
 * @param <T>
 * @since 0.2
 */
public abstract class AdapterContainer<T> implements InterfaceProvider {

    private Graph<Type, T> graph;
    private Graph<Type, T> implicitGraph;

    protected AdapterContainer() {
        this(new Graph<Type,T>(), new Graph<Type,T>());
    }

    protected AdapterContainer(Graph<Type, T> graph,
                               Graph<Type, T> implicitGraph) {
        this.graph = graph;
        this.implicitGraph = implicitGraph;

    }

    protected final void setGraphs(Graph<Type, T> graph,
                                   Graph<Type, T> implicitGraph) {
        this.graph = graph;
        this.implicitGraph = implicitGraph;
    }


    protected final void register(Adapter<?, ?>... adapters) {
        for (Adapter<?, ?> adapter : adapters) {
            Type typeTo = adapter.to();
            Type typeFrom = adapter.from();

            if(typeTo instanceof ParameterizedType){
                  typeTo = ((ParameterizedType)typeTo).getRawType();
            }
            if(typeFrom instanceof ParameterizedType){
                  typeFrom = ((ParameterizedType)typeFrom).getRawType();
            }

            T t = insertAdapter(adapter);
            graph.insert(typeTo, typeFrom, t);
            implicitGraph.insert(typeTo, typeFrom, t);

            if (typeTo instanceof Class) {
                Class toClass = (Class) typeTo;
                for (Class<?> to : toClass.getInterfaces()) {
                    implicitGraph.insert(to, typeFrom, t);

                    if (typeFrom instanceof Class) {
                        Class fromClass = (Class) typeFrom;
                        for (Class<?> from : fromClass.getInterfaces()) {
                            implicitGraph.insert(adapter.to(), from, t);
                            implicitGraph.insert(to, from, t);
                        }
                    }
                }
            }
        }
    }

    protected abstract T insertAdapter(Adapter<?, ?> adapter);



    protected final <U,V> T findAdapter(U context, Class<V> type) {

        Class<?> klass = context.getClass();
        T t = null;

        while (t == null && klass != null) {
            t = graph.get(type, klass);
            if (t == null) {
                t = implicitGraph.get(type, klass);
            }
            if (t == null) {
                for (Class<?> iface : klass.getInterfaces()) {
                    // todo check graph first
                    t = implicitGraph.get(type, iface);
                    if (t != null) {
                        break;
                    }
                }

            }
            klass = klass.getSuperclass();
        }

        if (t == null)
            throw new AdaptationException("Did not find any transformer");

        return t;

    }

    public final Class<?>[] interfaces(Class<?>... ifaces) {
        Collection<Class> interfaces = new HashSet<Class>();

        for (Class<?> iface : ifaces) {
            interfaces.add(iface);

            for (Type type : implicitGraph.to(iface)) {
                if (type instanceof Class) {
                    Class klass = (Class) type;

                    if (klass.isInterface())
                        interfaces.add(klass);

                    interfaces.addAll(Arrays.asList(klass.getInterfaces()));
                }
            }
        }
        return interfaces.toArray(new Class<?>[interfaces.size()]);
    }


    protected static class SimpleAdapterContainer extends AdapterContainer<Adapter> {

        @Override
        protected Adapter insertAdapter(Adapter<?, ?> adapter) {
            return adapter;
        }

        
    }
    

}
